home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 June: Reference Library / Dev.CD Jun 96 RL / Dev.CD Jun 96 RL.toast / What's New? / Development Kits / Apple Game Sprockets DR1 / Examples / DroneZone / DZGame.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-04-24  |  17.2 KB  |  735 lines  |  [TEXT/MPS ]

  1. /*
  2.  *    File:        DZGame.c
  3.  *    Author:        Dan Venolia
  4.  *
  5.  *    Contents:    Handles the game play.
  6.  *
  7.  *    Copyright © 1996 Apple Computer, Inc.
  8.  */
  9.  
  10. #include <assert.h>
  11. #include <string.h>
  12.  
  13. #include <Events.h>
  14. #include <Fonts.h>
  15. #include <QDOffscreen.h>
  16. #include <Timer.h>
  17. #include <Types.h>
  18.  
  19. #include <QD3D.h>
  20. #include <QD3DGeometry.h>
  21. #include <QD3DMath.h>
  22. #include <QD3DSet.h>
  23.  
  24. #include "SoundSprocket.h"
  25. #include "InputSprocket.h"
  26.  
  27. #include "DZDisplay.h"
  28. #include "DZDrone.h"
  29. #include "DZGame.h"
  30. #include "DZKeyCap.h"
  31. #include "DZSound.h"
  32. #include "DZSpace.h"
  33.  
  34.  
  35.  
  36. #define DEFAULT_INTERVAL    0.05        // Initial frame rate -- just a guess
  37. #define MAX_INTERVAL        0.25        // Limit on seconds per frame
  38.  
  39. #define KEY_ROTATE_RATE        1.0            // Radians per second while key is down
  40. #define INV_SQRT_TWO        0.707
  41.  
  42. #define AXIS_ROTATE_RATE    3.0            // Radians per second for full joystick throw
  43.  
  44.  
  45. enum {
  46.     kGameAutoDroneCount = 4                // Number of autopilot drones
  47. };
  48.  
  49.  
  50. float                        gGameInterval            = DEFAULT_INTERVAL;
  51. float                        gGameFramesPerSecond    = 1.0/DEFAULT_INTERVAL;
  52.  
  53. static unsigned long        gGameTime                = 0;        // Last frame time
  54.  
  55. static short                gGameDirectionalKeyCap    = 0;        // Last directional keycap pressed
  56. static Boolean                gGameDirectionalKeyDown    = false;    // Pressed recently?
  57.  
  58. static Boolean                gGameHUDVisible            = true;
  59.  
  60. static TDroneObject            gGameSelfDrone            = NULL;
  61.  
  62. static GWorldPtr            gGameFPSGWorld            = NULL;
  63. static TQ3GeometryObject    gGameFPSMarker            = NULL;
  64. static TQ3MarkerData        gGameFPSMarkerData;
  65. static Boolean                gGameFPSVisible            = false;
  66.  
  67. static TQ3GeometryObject    gGameCrossHairs            = NULL;
  68. static unsigned long        gGameCrossHairsData[32]    = {
  69.     0x00000000, 0x00000000, 0x00000000, 0x00000000,
  70.     0x00000000, 0x00010000, 0x00000000, 0x00010000,
  71.     0x00000000, 0x00054000, 0x00101000, 0x00228800,
  72.     0x00082000, 0x00400400, 0x00101000, 0x05400540,
  73.     0x00101000, 0x00400400, 0x00082000, 0x00228800,
  74.     0x00101000, 0x00054000, 0x00000000, 0x00010000,
  75.     0x00000000, 0x00010000, 0x00000000, 0x00000000,
  76.     0x00000000, 0x00000000, 0x00000000, 0x00000000
  77. };
  78.  
  79.  
  80. static void Game_DirectionalKey(
  81.     void);
  82.  
  83. static OSErr Game_TwoButtonJoystick(SInt32 *xAxis, SInt32 *yAxis, 
  84.                         SInt32 *POVHat, 
  85.                         Boolean *button1, Boolean *button2);
  86.  
  87. /* =============================================================================
  88.  *        Game_TwoButtonJoystick (internal)
  89.  *
  90.  *    Grab the joystick axes and buttons.
  91.  * ========================================================================== */
  92. OSErr Game_TwoButtonJoystick(SInt32 *xAxis, SInt32 *yAxis, 
  93.                         SInt32 *POVHat, 
  94.                         Boolean *button1, Boolean *button2)
  95. {
  96.     static UInt32 status = 0;
  97.     static ElementReference lastXAxis = nil;
  98.     static ElementReference lastYAxis = nil;
  99.     Boolean button1MustBeDown;
  100.     Boolean button2MustBeDown;
  101.     int maxEvents;
  102.     ElementEvent theEvent;
  103.     Boolean wasEvent;
  104.     OSErr err;
  105.     ElementListReference globalList;
  106.     ElementInfo info;
  107.         
  108.     *button1 = false;
  109.     *button2 = false;
  110.  
  111.     status = 0;
  112.     
  113.     button1MustBeDown = false;
  114.     button2MustBeDown = false;
  115.     
  116.     maxEvents = 10;            // only do events ten at a time, they might be continuously streaming out
  117.     wasEvent = true;
  118.     err = noErr;
  119.     
  120.     err = GetGlobalElementList(&globalList);
  121.  
  122.     while(maxEvents > 0)
  123.     {    
  124.         err = ElementListGetNextEvent(globalList, sizeof(theEvent), &theEvent, &wasEvent);
  125.         
  126.         if ((wasEvent == false) || (err))
  127.         {
  128.             break;
  129.         }
  130.         
  131.         // get information about this element since we are not doing any configuration
  132.         // we need to know the about labels and kinds
  133.         GetElementInfo(theEvent.element, &info);
  134.  
  135.         switch(info.theKind)
  136.         {
  137.             case kElementKindButton:
  138.             {
  139.                 ButtonConfigurationInfo buttonConfig;
  140.                 Boolean newValue;
  141.                 
  142.                 // figure out the new value for the boolean
  143.                 if (theEvent.data)
  144.                 {
  145.                     newValue = true;
  146.                 }
  147.                 else
  148.                 {
  149.                     newValue = false;
  150.                 }
  151.     
  152.                 // need configuration information to determine if this is button1 or not
  153.                 GetElementConfigurationInfo(theEvent.element, sizeof(buttonConfig), &buttonConfig);
  154.                                 
  155.                 // we will call button id 1 our buttons 1, usually the trigger
  156.                 if (buttonConfig.id == 1)
  157.                 {
  158.                     if (newValue)
  159.                     {
  160.                         *button1 = true;
  161.                     }
  162.                 }
  163.                 else
  164.                 {
  165.                     if (newValue)
  166.                     {
  167.                         *button2 = true;
  168.                     }
  169.                 }
  170.             }
  171.             break;
  172.             
  173.             case kElementKindDPad:
  174.             {
  175.                 if (info.theLabel == kElementLabelPOVHat)
  176.                 {
  177.                     *POVHat = theEvent.data;
  178.                 }
  179.                 else if (info.theLabel == kElementLabelPadMove)
  180.                 {
  181.                     // a movement DPad so they might be using
  182.                     // a console style direction pad so turn
  183.                     // that into axis style data
  184.                     
  185.                     lastXAxis = nil;
  186.                     lastYAxis = nil;
  187.                     
  188.                     switch(theEvent.data)
  189.                     {
  190.                         case kPadIdle:
  191.                             *xAxis = kAxisCenter;
  192.                             *yAxis = kAxisCenter;
  193.                             break;
  194.                         case kPadLeft:
  195.                             *xAxis = kAxisMinimum;
  196.                             *yAxis = kAxisCenter;
  197.                             break;
  198.                         case kPadUpLeft:
  199.                             *xAxis = kAxisMinimum;
  200.                             *yAxis = kAxisMaximum;
  201.                             break;
  202.                         case kPadUp:
  203.                             *xAxis = kAxisCenter;
  204.                             *yAxis = kAxisMaximum;
  205.                             break;
  206.                         case kPadUpRight:
  207.                             *xAxis = kAxisMaximum;
  208.                             *yAxis = kAxisMaximum;
  209.                             break;
  210.                         case kPadRight:
  211.                             *xAxis = kAxisMaximum;
  212.                             *yAxis = kAxisCenter;
  213.                             break;
  214.                         case kPadDownRight:
  215.                             *xAxis = kAxisMaximum;
  216.                             *yAxis = kAxisMinimum;
  217.                             break;
  218.                         case kPadDown:
  219.                             *xAxis = kAxisCenter;
  220.                             *yAxis = kAxisMinimum;
  221.                             break;
  222.                         case kPadDownLeft:
  223.                             *xAxis = kAxisMinimum;
  224.                             *yAxis = kAxisMinimum;
  225.                     }
  226.                 }
  227.             }
  228.             break;
  229.             
  230.             case kElementKindAxis:
  231.             {
  232.                 // if it is an axis find out if it is the
  233.                 // x or y axis style data and use that.
  234.                 
  235.                 if (info.theLabel == kElementLabelXAxis)
  236.                 {
  237.                     lastXAxis = theEvent.element;
  238.                     
  239.                     *xAxis = theEvent.data;
  240.                 }
  241.                 else if (info.theLabel == kElementLabelYAxis)
  242.                 {
  243.                     lastYAxis = theEvent.element;
  244.                     
  245.                     *yAxis = theEvent.data;
  246.                 }
  247.             }
  248.             break;
  249.         }
  250.         
  251.         maxEvents--;
  252.     }
  253.     
  254.     // poll the last meaningful axis we had on the way out
  255.     // just to make sure we had good data
  256.     if (lastXAxis != nil)
  257.     {
  258.         GetElementSimpleState(lastXAxis, xAxis);
  259.     }
  260.     
  261.     if (lastYAxis != nil)
  262.     {
  263.         GetElementSimpleState(lastYAxis, yAxis);
  264.     }
  265.     
  266.     return err;
  267. }
  268.  
  269.  
  270. /* =============================================================================
  271.  *        Game_Init (external)
  272.  *
  273.  *    Initializes the game stuff.
  274.  * ========================================================================== */
  275. void Game_Init(
  276.     void)
  277. {
  278.     int                    droneNum;
  279.     Rect                bounds;
  280.     PixMapHandle        pixMapHandle;
  281.     CGrafPtr            savePort;
  282.     GDHandle            saveGDevice;
  283.     TQ3MarkerData        markerData;
  284.     TQ3ColorRGB            color;
  285.     
  286.     // Create the drone for the player
  287.     gGameSelfDrone = SelfDrone_New();
  288.     
  289.     // Create some automatic drones
  290.     for (droneNum = 0; droneNum < kGameAutoDroneCount; droneNum++)
  291.     {
  292.         AutoDrone_New(gGameSelfDrone);
  293.     }
  294.     
  295.     // Set up the 3D sound listener
  296.     Sound_GetListener();
  297.     
  298.     // Create the FPS marker
  299.     bounds.top    = 0;
  300.     bounds.left   = 0;
  301.     bounds.bottom = 12;
  302.     bounds.right  = 31;
  303.     
  304.     gGameFPSGWorld = NULL;
  305.     NewGWorld(&gGameFPSGWorld, 1, &bounds, NULL, NULL, 0);
  306.     assert(gGameFPSGWorld != NULL);
  307.     
  308.     LockPixels(gGameFPSGWorld->portPixMap);
  309.     
  310.     GetGWorld(&savePort, &saveGDevice);
  311.     SetGWorld(gGameFPSGWorld, NULL);
  312.     
  313.     TextFont(geneva);
  314.     TextSize(10);
  315.     
  316.     EraseRect(&gGameFPSGWorld->portRect);
  317.     
  318.     SetGWorld(savePort, saveGDevice);
  319.     
  320.     pixMapHandle = GetGWorldPixMap(gGameFPSGWorld);
  321.     
  322.     gGameFPSMarkerData.location.x            = 0.0;
  323.     gGameFPSMarkerData.location.y            = 0.0;
  324.     gGameFPSMarkerData.location.z            = 0.0;
  325.     gGameFPSMarkerData.xOffset                = -(bounds.left+bounds.right+1 >> 1);
  326.     gGameFPSMarkerData.yOffset                = -(bounds.top+bounds.bottom+1 >> 1);
  327.     gGameFPSMarkerData.bitmap.image            = (unsigned char*) GetPixBaseAddr(pixMapHandle);
  328.     gGameFPSMarkerData.bitmap.width            = bounds.right-bounds.left;
  329.     gGameFPSMarkerData.bitmap.height        = bounds.bottom-bounds.top;
  330.     gGameFPSMarkerData.bitmap.rowBytes        = (*pixMapHandle)->rowBytes & 0x00003FFF;
  331.     gGameFPSMarkerData.bitmap.bitOrder        = kQ3EndianBig;
  332.     gGameFPSMarkerData.markerAttributeSet    = Q3AttributeSet_New();
  333.     assert(gGameFPSMarkerData.markerAttributeSet != NULL);
  334.     
  335.     color.r = 1.0;
  336.     color.g = 1.0;
  337.     color.b = 0.4;
  338.     
  339.     Q3AttributeSet_Add(gGameFPSMarkerData.markerAttributeSet, kQ3AttributeTypeDiffuseColor, &color);
  340.     
  341.     gGameFPSMarker = Q3Marker_New(&gGameFPSMarkerData);
  342.     
  343.     // Create the crosshairs
  344.     markerData.location.x            = 0.0;
  345.     markerData.location.y            = 0.0;
  346.     markerData.location.z            = 0.0;
  347.     markerData.xOffset                = -15;
  348.     markerData.yOffset                = -15;
  349.     markerData.bitmap.image            = (unsigned char*) gGameCrossHairsData;
  350.     markerData.bitmap.width            = 31;  //• SHOULD BE 32, BUT TO GET AROUND A APPLE QD3D ACCEL CARD DRIVER BUG...
  351.     markerData.bitmap.height        = 32;
  352.     markerData.bitmap.rowBytes        = 4;
  353.     markerData.bitmap.bitOrder        = kQ3EndianBig;
  354.     markerData.markerAttributeSet    = Q3AttributeSet_New();
  355.     assert(markerData.markerAttributeSet != NULL);
  356.     
  357.     color.r = 1.0;
  358.     color.g = 1.0;
  359.     color.b = 0.4;
  360.     
  361.     Q3AttributeSet_Add(markerData.markerAttributeSet, kQ3AttributeTypeDiffuseColor, &color);
  362.     
  363.     gGameCrossHairs = Q3Marker_New(&markerData);
  364.     
  365.     Q3Object_Dispose(markerData.markerAttributeSet);
  366.     markerData.markerAttributeSet = NULL;
  367. }
  368.  
  369.  
  370. /* =============================================================================
  371.  *        Game_Exit (external)
  372.  *
  373.  *    Prepares for exit.
  374.  * ========================================================================== */
  375. void Game_Exit(
  376.     void)
  377. {
  378.     TDroneObject        drone;
  379.     
  380.     while ((drone = Drone_Next(NULL)) != NULL)
  381.     {
  382.         Drone_Dispose(drone);
  383.     }
  384.     
  385.     if (gGameFPSMarker != NULL)
  386.     {
  387.         Q3Object_Dispose(gGameFPSMarker);
  388.         gGameFPSMarker = NULL;
  389.     }
  390.     
  391.     if (gGameCrossHairs != NULL)
  392.     {
  393.         Q3Object_Dispose(gGameCrossHairs);
  394.         gGameCrossHairs = NULL;
  395.     }
  396. }
  397.  
  398.  
  399. /* =============================================================================
  400.  *        Game_KeyDown (external)
  401.  *
  402.  *    Handles a key press.  The fire button happens immediately.  The most
  403.  *    recently pressed of the directional keys is remembered for use in
  404.  *    Game_DirectionalKey.
  405.  * ========================================================================== */
  406. void Game_KeyDown(
  407.     unsigned long    inChar,
  408.     unsigned long    inKeyCap)
  409. {
  410.     switch (inKeyCap)
  411.     {
  412.         case kKeyCap_Numeric1:
  413.         case kKeyCap_Numeric2:
  414.         case kKeyCap_Numeric3:
  415.         case kKeyCap_Numeric4:
  416.         case kKeyCap_Numeric6:
  417.         case kKeyCap_Numeric7:
  418.         case kKeyCap_Numeric8:
  419.         case kKeyCap_Numeric9:
  420.             gGameDirectionalKeyCap = inKeyCap;
  421.             gGameDirectionalKeyDown = true;
  422.         break;
  423.         
  424.         case kKeyCap_SpaceBar:
  425.             Drone_Fire(gGameSelfDrone);
  426.         break;
  427.         
  428.         default:
  429.             switch (inChar)
  430.             {
  431.                 case 'h':
  432.                 case 'H':
  433.                     gGameHUDVisible = !gGameHUDVisible;
  434.                 break;
  435.                 
  436.                 case 'f':
  437.                 case 'F':
  438.                     gGameFPSVisible = !gGameFPSVisible;
  439.                 break;
  440.             }
  441.     }
  442. }
  443.  
  444.  
  445. /* =============================================================================
  446.  *        Game_Process (external)
  447.  *
  448.  *    Handles idle time by moving the game ahead one time step.  Only called if
  449.  *    the game is active.
  450.  * ========================================================================== */
  451. void Game_Process(
  452.     void)
  453. {
  454.     UnsignedWide        wide;
  455.     unsigned long        now;
  456.     Str15                str;
  457.     CGrafPtr            savePort;
  458.     GDHandle            saveGDevice;
  459.     TDroneObject        drone;
  460.     TDroneObject        next;
  461.     TQ3Point3D            position;
  462.     TQ3Vector3D            direction;
  463.     TQ3Vector3D            up;
  464.     TQ3Matrix4x4        matrix;
  465.     SInt32                xAxis;
  466.     SInt32                yAxis;
  467.     SInt32                POVHat;
  468.     Boolean                button1;
  469.     Boolean                button2;
  470.     
  471.     // Find the frame rate
  472.     Microseconds(&wide);
  473.     now = wide.lo;
  474.     
  475.     if (gGameTime != 0)
  476.     {
  477.         // Find the interval for the last frame
  478.         gGameInterval = 0.000001*(now-gGameTime);
  479.         
  480.         // Limit frame rate to a resonable number
  481.         if (gGameInterval > MAX_INTERVAL)
  482.         {
  483.             gGameInterval = MAX_INTERVAL;
  484.         }
  485.         
  486.         // Find corresponding frames per second
  487.         gGameFramesPerSecond = 1.0/gGameInterval;
  488.     }
  489.     
  490.     gGameTime = now;
  491.     
  492.     // Update the FPS marker
  493.     if (gGameFPSVisible)
  494.     {
  495.         sprintf((char*) str, "x%.1f", gGameFramesPerSecond);
  496.         str[0] = strlen((char*) str) - 1;
  497.         
  498.         GetGWorld(&savePort, &saveGDevice);
  499.         SetGWorld(gGameFPSGWorld, NULL);
  500.         
  501.         EraseRect(&gGameFPSGWorld->portRect);
  502.         
  503.         MoveTo(gGameFPSGWorld->portRect.left+gGameFPSGWorld->portRect.right-StringWidth(str) >> 1, 10);
  504.         DrawString(str);
  505.         
  506.         SetGWorld(savePort, saveGDevice);
  507.         
  508.         Q3Marker_SetBitmap(gGameFPSMarker, &gGameFPSMarkerData.bitmap);
  509.     }
  510.     
  511.     // Change direction while key is held down
  512.     Game_DirectionalKey();
  513.     
  514.     // use the InputSprocket    
  515.     Game_TwoButtonJoystick(&xAxis, &yAxis, &POVHat, &button1, &button2);
  516.     
  517.     SelfDrone_Turn(
  518.             gGameSelfDrone,
  519.             ((float) xAxis / (float) kAxisMaximum)*AXIS_ROTATE_RATE*gGameInterval,
  520.             ((float) yAxis / (float) kAxisMaximum)*AXIS_ROTATE_RATE*gGameInterval);
  521.     
  522.     if (button1)
  523.     {
  524.         Drone_Fire(gGameSelfDrone);
  525.     }
  526.     
  527.     if (button2)
  528.     {
  529.         gGameHUDVisible = !gGameHUDVisible;
  530.     }
  531.     
  532.     // Move all the drones, mark any to be deleted
  533.     for (drone = Drone_Next(NULL); drone != NULL; drone = Drone_Next(drone))
  534.     {
  535.         Drone_Move(drone);
  536.     }
  537.     
  538.     // Delete the marked drones
  539.     drone = Drone_Next(NULL);
  540.     while (drone != NULL)
  541.     {
  542.         next = Drone_Next(drone);
  543.         
  544.         if (Drone_GetMark(drone))
  545.         {
  546.             Drone_Dispose(drone);
  547.         }
  548.         
  549.         drone = next;
  550.     }
  551.     
  552.     // Move the viewer to follow the self drone
  553.     Drone_GetPosition(gGameSelfDrone, &position);
  554.     Drone_GetDirection(gGameSelfDrone, &direction);
  555.     Drone_GetUp(gGameSelfDrone, &up);
  556.     
  557.     Display_SetViewerPosition(&position, &direction, &up);
  558.     
  559.     // Move the listener to follow the self drone
  560.     Drone_GetMatrix(gGameSelfDrone, &matrix);
  561.     SetListenerTransform(Sound_GetListener(), &matrix);
  562.     
  563.     // Change the localized sounds
  564.     for (drone = Drone_Next(NULL); drone != NULL; drone = Drone_Next(drone))
  565.     {
  566.         Drone_UpdateSound(drone);
  567.     }
  568. }
  569.  
  570.  
  571. /* =============================================================================
  572.  *        Game_Submit (external)
  573.  *
  574.  *    Submits all the 3D geometry of the game.
  575.  * ========================================================================== */
  576. void Game_Submit(
  577.     TQ3ViewObject        inView)
  578. {
  579.     TDroneObject        drone;
  580.     TQ3Point3D            position;
  581.     TQ3Vector3D            direction;
  582.     TQ3Vector3D            up;
  583.     TQ3Vector3D            v;
  584.     TQ3Point3D            markerPosition;
  585.     
  586.     assert(inView != NULL);
  587.     
  588.     // Submit the drones
  589.     for (drone = Drone_Next(NULL); drone != NULL; drone = Drone_Next(drone))
  590.     {
  591.         Drone_Submit(drone, gGameHUDVisible, inView);
  592.     }
  593.     
  594.     // Get information about the camera position
  595.     Drone_GetPosition(gGameSelfDrone, &position);
  596.     Drone_GetDirection(gGameSelfDrone, &direction);
  597.     Drone_GetUp(gGameSelfDrone, &up);
  598.     
  599.     // Submit the spacejunk
  600.     Space_Submit(inView, &position, &direction);
  601.     
  602.     // Submit the FPS marker if the caps lock key is down
  603.     if (gGameFPSVisible)
  604.     {
  605.         Q3Point3D_Vector3D_Add(&position, &direction, &markerPosition);
  606.         Q3Vector3D_Scale(&up, -0.6, &v);
  607.         Q3Point3D_Vector3D_Add(&markerPosition, &v, &markerPosition);
  608.         Q3Marker_SetPosition(gGameFPSMarker, &markerPosition);
  609.         Q3Object_Submit(gGameFPSMarker, inView);
  610.     }
  611.     
  612.     // Submit the crosshairs
  613.     if (gGameHUDVisible)
  614.     {
  615.         Q3Point3D_Vector3D_Add(&position, &direction, &markerPosition);
  616.         Q3Marker_SetPosition(gGameCrossHairs, &markerPosition);
  617.         Q3Object_Submit(gGameCrossHairs, inView);
  618.     }
  619. }
  620.  
  621.  
  622. /* =============================================================================
  623.  *        Game_DirectionalKey (internal)
  624.  *
  625.  *    Takes action based on the currently pressed directional key.  If the most
  626.  *    recently pressed key is still down, or if this is the first time for the
  627.  *    key, then the main drone is turned.
  628.  * ========================================================================== */
  629. void Game_DirectionalKey(
  630.     void)
  631. {
  632.     Boolean                keyIsDown;
  633.     KeyMap                keyMap;
  634.     unsigned long        index;
  635.     unsigned long        mask;
  636.     float                horz;
  637.     float                vert;
  638.     
  639.     // Do we even have a key to check?
  640.     if (gGameDirectionalKeyCap != 0)
  641.     {
  642.         // Check the state of the directional key that went down last
  643.         keyIsDown = false;
  644.         
  645.         if (gGameDirectionalKeyDown)
  646.         {
  647.             // Key was pressed on this turn
  648.             keyIsDown = true;
  649.             gGameDirectionalKeyDown = false;
  650.         }
  651.         else
  652.         {
  653.             // Key was pressed some time ago -- is it still down?
  654.             GetKeys(keyMap);
  655.             
  656.             // KeyMap is scrambled because of funky old Pascal bit packing rules.
  657.             // The keycap may be decomposed as follows:
  658.             //
  659.             //        +---+---+---+---|---+---+---+---+
  660.             //        | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
  661.             //        +---+---+---+---|---+---+---+---+
  662.             //        | 0 | index | ~byte |    bit    |
  663.             //        +---+---+---+---|---+---+---+---+
  664.             
  665.             index = gGameDirectionalKeyCap >> 5 & 0x03;
  666.             mask = 1 << ((gGameDirectionalKeyCap & 0x1F) ^ 0x18);
  667.             
  668.             if (keyMap[index] & mask)
  669.             {
  670.                 // Key is still down
  671.                 keyIsDown = true;
  672.             }
  673.             else
  674.             {
  675.                 // The key has been released
  676.                 gGameDirectionalKeyCap = 0;
  677.                 
  678.                 //• TODO: Search for any other directional key down
  679.             }
  680.         }
  681.         
  682.         // Do the action while the directional key is still down
  683.         if (keyIsDown)
  684.         {
  685.             // Find the amount of turn
  686.             horz = 0.0;
  687.             vert = 0.0;
  688.             
  689.             switch (gGameDirectionalKeyCap)
  690.             {
  691.                 case kKeyCap_Numeric1:
  692.                     horz = -INV_SQRT_TWO*KEY_ROTATE_RATE;
  693.                     vert = horz;
  694.                 break;
  695.                 
  696.                 case kKeyCap_Numeric2:
  697.                     vert = -KEY_ROTATE_RATE;
  698.                 break;
  699.                 
  700.                 case kKeyCap_Numeric3:
  701.                     horz = INV_SQRT_TWO*KEY_ROTATE_RATE;
  702.                     vert = -horz;
  703.                 break;
  704.                 
  705.                 case kKeyCap_Numeric4:
  706.                     horz = -KEY_ROTATE_RATE;
  707.                 break;
  708.                 
  709.                 case kKeyCap_Numeric6:
  710.                     horz = KEY_ROTATE_RATE;
  711.                 break;
  712.                 
  713.                 case kKeyCap_Numeric7:
  714.                     vert = INV_SQRT_TWO*KEY_ROTATE_RATE;
  715.                     horz = -vert;
  716.                 break;
  717.                 
  718.                 case kKeyCap_Numeric8:
  719.                     vert = KEY_ROTATE_RATE;
  720.                 break;
  721.                 
  722.                 case kKeyCap_Numeric9:
  723.                     horz = INV_SQRT_TWO*KEY_ROTATE_RATE;
  724.                     vert = horz;
  725.                 break;
  726.             }
  727.             
  728.             // Execute the turn
  729.             SelfDrone_Turn(gGameSelfDrone, horz*gGameInterval, vert*gGameInterval);
  730.         }
  731.     }
  732. }
  733.  
  734.  
  735.